/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.demo; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.res.AssetManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.util.JsonReader; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseExpandableListAdapter; import android.widget.ExpandableListView; import android.widget.ExpandableListView.OnChildClickListener; import android.widget.TextView; import android.widget.Toast; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSourceInputStream; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DefaultDataSource; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; /** * An activity for selecting from a list of samples. */ public class SampleChooserActivity extends Activity { private static final String TAG = "SampleChooserActivity"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.sample_chooser_activity); Intent intent = getIntent(); String dataUri = intent.getDataString(); String[] uris; if (dataUri != null) { uris = new String[] {dataUri}; } else { ArrayList<String> uriList = new ArrayList<>(); AssetManager assetManager = getAssets(); try { for (String asset : assetManager.list("")) { if (asset.endsWith(".exolist.json")) { uriList.add("asset:///" + asset); } } } catch (IOException e) { Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG) .show(); } uris = new String[uriList.size()]; uriList.toArray(uris); Arrays.sort(uris); } SampleListLoader loaderTask = new SampleListLoader(); loaderTask.execute(uris); } private void onSampleGroups(final List<SampleGroup> groups, boolean sawError) { if (sawError) { Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG) .show(); } ExpandableListView sampleList = (ExpandableListView) findViewById(R.id.sample_list); sampleList.setAdapter(new SampleAdapter(this, groups)); sampleList.setOnChildClickListener(new OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View view, int groupPosition, int childPosition, long id) { onSampleSelected(groups.get(groupPosition).samples.get(childPosition)); return true; } }); } private void onSampleSelected(Sample sample) { startActivity(sample.buildIntent(this)); } private final class SampleListLoader extends AsyncTask<String, Void, List<SampleGroup>> { private boolean sawError; @Override protected List<SampleGroup> doInBackground(String... uris) { List<SampleGroup> result = new ArrayList<>(); Context context = getApplicationContext(); String userAgent = Util.getUserAgent(context, "ExoPlayerDemo"); DataSource dataSource = new DefaultDataSource(context, null, userAgent, false); for (String uri : uris) { DataSpec dataSpec = new DataSpec(Uri.parse(uri)); InputStream inputStream = new DataSourceInputStream(dataSource, dataSpec); try { readSampleGroups(new JsonReader(new InputStreamReader(inputStream, "UTF-8")), result); } catch (Exception e) { Log.e(TAG, "Error loading sample list: " + uri, e); sawError = true; } finally { Util.closeQuietly(dataSource); } } return result; } @Override protected void onPostExecute(List<SampleGroup> result) { onSampleGroups(result, sawError); } private void readSampleGroups(JsonReader reader, List<SampleGroup> groups) throws IOException { reader.beginArray(); while (reader.hasNext()) { readSampleGroup(reader, groups); } reader.endArray(); } private void readSampleGroup(JsonReader reader, List<SampleGroup> groups) throws IOException { String groupName = ""; ArrayList<Sample> samples = new ArrayList<>(); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); switch (name) { case "name": groupName = reader.nextString(); break; case "samples": reader.beginArray(); while (reader.hasNext()) { samples.add(readEntry(reader, false)); } reader.endArray(); break; case "_comment": reader.nextString(); // Ignore. break; default: throw new ParserException("Unsupported name: " + name); } } reader.endObject(); SampleGroup group = getGroup(groupName, groups); group.samples.addAll(samples); } private Sample readEntry(JsonReader reader, boolean insidePlaylist) throws IOException { String sampleName = null; String uri = null; String extension = null; UUID drmUuid = null; String drmLicenseUrl = null; String[] drmKeyRequestProperties = null; boolean preferExtensionDecoders = false; ArrayList<UriSample> playlistSamples = null; reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); switch (name) { case "name": sampleName = reader.nextString(); break; case "uri": uri = reader.nextString(); break; case "extension": extension = reader.nextString(); break; case "drm_scheme": Assertions.checkState(!insidePlaylist, "Invalid attribute on nested item: drm_scheme"); drmUuid = getDrmUuid(reader.nextString()); break; case "drm_license_url": Assertions.checkState(!insidePlaylist, "Invalid attribute on nested item: drm_license_url"); drmLicenseUrl = reader.nextString(); break; case "drm_key_request_properties": Assertions.checkState(!insidePlaylist, "Invalid attribute on nested item: drm_key_request_properties"); ArrayList<String> drmKeyRequestPropertiesList = new ArrayList<>(); reader.beginObject(); while (reader.hasNext()) { drmKeyRequestPropertiesList.add(reader.nextName()); drmKeyRequestPropertiesList.add(reader.nextString()); } reader.endObject(); drmKeyRequestProperties = drmKeyRequestPropertiesList.toArray(new String[0]); break; case "prefer_extension_decoders": Assertions.checkState(!insidePlaylist, "Invalid attribute on nested item: prefer_extension_decoders"); preferExtensionDecoders = reader.nextBoolean(); break; case "playlist": Assertions.checkState(!insidePlaylist, "Invalid nesting of playlists"); playlistSamples = new ArrayList<>(); reader.beginArray(); while (reader.hasNext()) { playlistSamples.add((UriSample) readEntry(reader, true)); } reader.endArray(); break; default: throw new ParserException("Unsupported attribute name: " + name); } } reader.endObject(); if (playlistSamples != null) { UriSample[] playlistSamplesArray = playlistSamples.toArray( new UriSample[playlistSamples.size()]); return new PlaylistSample(sampleName, drmUuid, drmLicenseUrl, drmKeyRequestProperties, preferExtensionDecoders, playlistSamplesArray); } else { return new UriSample(sampleName, drmUuid, drmLicenseUrl, drmKeyRequestProperties, preferExtensionDecoders, uri, extension); } } private SampleGroup getGroup(String groupName, List<SampleGroup> groups) { for (int i = 0; i < groups.size(); i++) { if (Util.areEqual(groupName, groups.get(i).title)) { return groups.get(i); } } SampleGroup group = new SampleGroup(groupName); groups.add(group); return group; } private UUID getDrmUuid(String typeString) throws ParserException { switch (Util.toLowerInvariant(typeString)) { case "widevine": return C.WIDEVINE_UUID; case "playready": return C.PLAYREADY_UUID; case "cenc": return C.CLEARKEY_UUID; default: try { return UUID.fromString(typeString); } catch (RuntimeException e) { throw new ParserException("Unsupported drm type: " + typeString); } } } } private static final class SampleAdapter extends BaseExpandableListAdapter { private final Context context; private final List<SampleGroup> sampleGroups; public SampleAdapter(Context context, List<SampleGroup> sampleGroups) { this.context = context; this.sampleGroups = sampleGroups; } @Override public Sample getChild(int groupPosition, int childPosition) { return getGroup(groupPosition).samples.get(childPosition); } @Override public long getChildId(int groupPosition, int childPosition) { return childPosition; } @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { View view = convertView; if (view == null) { view = LayoutInflater.from(context).inflate(android.R.layout.simple_list_item_1, parent, false); } ((TextView) view).setText(getChild(groupPosition, childPosition).name); return view; } @Override public int getChildrenCount(int groupPosition) { return getGroup(groupPosition).samples.size(); } @Override public SampleGroup getGroup(int groupPosition) { return sampleGroups.get(groupPosition); } @Override public long getGroupId(int groupPosition) { return groupPosition; } @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { View view = convertView; if (view == null) { view = LayoutInflater.from(context).inflate(android.R.layout.simple_expandable_list_item_1, parent, false); } ((TextView) view).setText(getGroup(groupPosition).title); return view; } @Override public int getGroupCount() { return sampleGroups.size(); } @Override public boolean hasStableIds() { return false; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } } private static final class SampleGroup { public final String title; public final List<Sample> samples; public SampleGroup(String title) { this.title = title; this.samples = new ArrayList<>(); } } private abstract static class Sample { public final String name; public final boolean preferExtensionDecoders; public final UUID drmSchemeUuid; public final String drmLicenseUrl; public final String[] drmKeyRequestProperties; public Sample(String name, UUID drmSchemeUuid, String drmLicenseUrl, String[] drmKeyRequestProperties, boolean preferExtensionDecoders) { this.name = name; this.drmSchemeUuid = drmSchemeUuid; this.drmLicenseUrl = drmLicenseUrl; this.drmKeyRequestProperties = drmKeyRequestProperties; this.preferExtensionDecoders = preferExtensionDecoders; } public Intent buildIntent(Context context) { Intent intent = new Intent(context, PlayerActivity.class); intent.putExtra(PlayerActivity.PREFER_EXTENSION_DECODERS, preferExtensionDecoders); if (drmSchemeUuid != null) { intent.putExtra(PlayerActivity.DRM_SCHEME_UUID_EXTRA, drmSchemeUuid.toString()); intent.putExtra(PlayerActivity.DRM_LICENSE_URL, drmLicenseUrl); intent.putExtra(PlayerActivity.DRM_KEY_REQUEST_PROPERTIES, drmKeyRequestProperties); } return intent; } } private static final class UriSample extends Sample { public final String uri; public final String extension; public UriSample(String name, UUID drmSchemeUuid, String drmLicenseUrl, String[] drmKeyRequestProperties, boolean preferExtensionDecoders, String uri, String extension) { super(name, drmSchemeUuid, drmLicenseUrl, drmKeyRequestProperties, preferExtensionDecoders); this.uri = uri; this.extension = extension; } @Override public Intent buildIntent(Context context) { return super.buildIntent(context) .setData(Uri.parse(uri)) .putExtra(PlayerActivity.EXTENSION_EXTRA, extension) .setAction(PlayerActivity.ACTION_VIEW); } } private static final class PlaylistSample extends Sample { public final UriSample[] children; public PlaylistSample(String name, UUID drmSchemeUuid, String drmLicenseUrl, String[] drmKeyRequestProperties, boolean preferExtensionDecoders, UriSample... children) { super(name, drmSchemeUuid, drmLicenseUrl, drmKeyRequestProperties, preferExtensionDecoders); this.children = children; } @Override public Intent buildIntent(Context context) { String[] uris = new String[children.length]; String[] extensions = new String[children.length]; for (int i = 0; i < children.length; i++) { uris[i] = children[i].uri; extensions[i] = children[i].extension; } return super.buildIntent(context) .putExtra(PlayerActivity.URI_LIST_EXTRA, uris) .putExtra(PlayerActivity.EXTENSION_LIST_EXTRA, extensions) .setAction(PlayerActivity.ACTION_VIEW_LIST); } } }